今天來用 canvas 做一個 手機解鎖的模擬功能~
onTouchStart={handleStart}
onTouchMove={handleMove}
onTouchEnd={handleEnd}
要注意的是! 因為是手機模擬功能
所以使用的 是 touchmove
,
而如果要用一般電腦裝置滑鼠效果的話,
要改成mousemove
,
然後將有判斷 event.touches[0]
的部分修改掉就好。
import React, { useEffect, useState, useRef } from "react";
const canvasSize = 500;
let linewidth = 2; //畫筆(線)寬度
let signal = 0;
let suceessCount = 0;
var initPosition = 0;
const radius = 0.05 * canvasSize; // 半徑
const wgap = (canvasSize - 3 * 2 * (radius + linewidth)) / 6;
const hgap = (canvasSize - 3 * 2 * (radius + linewidth)) / 6;
var position = [];
var touchPoints = [];
const CanvasLock = () => {
const canvasRef = useRef(null);
const [ninepoints, setNinepoints] = useState([]);
const [result, setResult] = useState(null);
useEffect(() => {
if (canvasRef.current) {
initCanvas();
}
}, [canvasRef]);
const handleStart = (event) => {
let targets = event.touches;
if (targets.length === 1) {
isPointselect(targets[0]);
}
};
const handleMove = (event) => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
// event.preventDefault(); // 阻止默認行為
var target = event.touches[0], // 單指
touches = event.targetTouches;
if (touches.length !== 1) return;
// 點擊位置
const point = getClientOffset(target);
clearcanvas();
let beforecurr;
if (event) {
// 已滑過的點
let oldpoints = touchPoints;
let oldlen = oldpoints.length;
let maxlen;
isPointselect(target);
let newlen = touchPoints.length;
if (initPosition === 0) {
//加入第一個點
position.push(ninepoints[touchPoints[0]]?.x);
position.push(ninepoints[touchPoints[0]]?.y);
initPosition++;
}
// 新的點
if (newlen > oldlen) {
maxlen = newlen - 1;
beforecurr = touchPoints[maxlen - 1];
let currlen = touchPoints[maxlen];
position.push(ninepoints[currlen]?.x);
position.push(ninepoints[currlen]?.y);
}
// 線跟著手指移動
if (newlen === oldlen) {
// 每次滑過都先清空畫布,再更新
maxlen = newlen;
beforecurr = touchPoints[maxlen - 1];
setTimeout(ctx.clearRect(0, 0, canvasSize, canvasSize), 500);
initCanvas();
// 連接畫過的點,並劃上中心的小圓 add point
for (let i = 0; i < position.length; i += 2) {
ctx.beginPath();
ctx.lineWidth = 6;
ctx.strokeStyle = "white";
ctx.arc(
position[i],
position[i + 1],
(8 / 25) * radius,
0,
2 * Math.PI,
false
);
ctx.fillStyle = "white";
ctx.fill();
ctx.moveTo(position[i], position[i + 1]);
ctx.lineTo(position[i + 2], position[i + 3]);
ctx.stroke();
}
// 跟著手指移動增加線條 add line
ctx.beginPath();
ctx.lineWidth = 6;
ctx.strokeStyle = "white";
ctx.moveTo(ninepoints[beforecurr]?.x, ninepoints[beforecurr]?.y);
ctx.lineTo(point?.x, point?.y);
ctx.stroke();
ctx.closePath();
}
}
};
const handleEnd = (event) => {
suceessCount++;
//重置數據
// position = [];
initPosition = 0;
// 設定密碼
if (signal === 0) {
if (touchPoints.length < 5) {
suceessCount = 0;
setTimeout(clearcanvas, 500);
} else {
if (suceessCount === 1) {
//成功一次,清空重新初始化
// setResult(touchPoints.join(""));
setTimeout(clearcanvas, 500);
} else if (suceessCount === 2) {
//第二次
if (result !== touchPoints.join("")) {
// 如果兩次不一樣
setResult(0);
suceessCount = 0;
setTimeout(clearcanvas, 500);
} else if (result === touchPoints.join("")) {
localStorage[0] = result;
setResult(0);
suceessCount = 0;
setTimeout(clearcanvas, 500);
}
}
}
}
// 驗證密碼
else if (signal === 1) {
suceessCount = 0;
// result = touchPoints.join("");
clearcanvas();
if (localStorage[0] == null) {
setTimeout(clearcanvas, 500);
} else {
if (localStorage[0] !== result) {
setTimeout(clearcanvas, 500);
} else if (localStorage[0] === result) {
}
}
}
};
const isPointselect = (target) => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
let powx, powy, gap;
// 點擊座標
const point = getClientOffset(target);
for (let i = 0; i < ninepoints.length; i++) {
powx = Math.pow(ninepoints[i].x - point?.x, 2);
powy = Math.pow(ninepoints[i].y - point?.y, 2);
gap = Math.sqrt(powx + powy);
if (gap < radius) {
// 點擊到圓心距離小於半徑時,觸發事件
if (touchPoints.indexOf(i) === -1) {
// 值 i 是否存在陣列中
// 沒有滑過的點保存
// setTouchPoints((prev) => [...prev, i]);
touchPoints.push(i);
// 只點一下畫小圓
ctx.beginPath();
ctx.fillStyle = "white";
ctx.arc(
ninepoints[touchPoints[0]]?.x,
ninepoints[touchPoints[0]]?.y,
8,
0,
2 * Math.PI,
false
);
ctx.fill();
}
break; //結束循環
}
}
};
/** 取得位置 */
const getClientOffset = (event) => {
let rect = canvasRef.current.getBoundingClientRect();
const point = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
return point;
};
/** 初始畫布 畫 9 個圓 */
function initCanvas(w, h) {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
const arr = circleCenters(w, h);
let len = arr.length;
setNinepoints(arr);
ctx.strokeStyle = "white";
ctx.lineWidth = linewidth;
for (let i = 0; i < len; i++) {
ctx.beginPath();
ctx.arc(arr[i].x, arr[i].y, radius, 0, Math.PI * 2, false);
ctx.stroke();
}
}
//計算圓心位置
function circleCenters() {
const points = [];
for (let col = 0; col < 3; col++) {
for (let row = 0; row < 3; row++) {
const point = {
x: wgap * 2 + (radius + linewidth) * (row * 2 + 1) + wgap * row,
y: canvasSize / 6 + (radius + linewidth) * (col * 2 + 1) + hgap * col
};
points.push(point);
}
}
return points;
}
/** 清空畫布 ,並重新繪製 */
const clearcanvas = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvasSize, canvasSize);
initCanvas();
};
return (
<canvas
ref={canvasRef}
width={canvasSize}
height={canvasSize}
onTouchStart={handleStart}
onTouchMove={handleMove}
onTouchEnd={handleEnd}
></canvas>
);
};
export default CanvasLock;